Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
A library for creating UI components using virtual DOM as an alternative to React. Deku has a smaller footprint (~8kb), a functional API, and doesn't support legacy browsers.
To install:
npm install deku
You can also use Duo, Bower or download the files manually.
Components are just plain objects that have a render function instead of using classes or constructors:
// button.js
let Button = {
render (component) {
let {props, state} = component
return <button class="Button" type={props.kind}>{props.children}</button>
},
afterUpdate (component, prevProps, prevState, updateState) {
let {props, state} = component
if (!state.clicked) {
updateState({ clicked: true })
}
}
}
export {Button}
Components are then rendered by mounting it in a tree:
import {Button} from './button'
import {tree,render,renderString} from 'deku'
let app = tree(
<Button kind="submit">Hello World!</Button>
)
render(app, document.body)
Trees can be rendered on the server too:
let str = renderString(app)
Each element of your UI can be broken into encapsulated components. These components manage the state for the UI element and tell it how to render. In Deku components are just plain objects:
function render (component) {
let {props, state} = component
return <button class="Button">{props.children}</button>
}
export default {render}
There is no concept of classes or use of this
. We can import this component using the standard module syntax:
import Button from './button'
To render this to the DOM we need to create a tree
. This is one of the other main differences between React and Deku. The tree
will manage loading data, communicating between components and allows us to use plugins on the entire application.
import {element,tree} from 'deku'
var app = tree(<Button>Hello World</Button>)
The app
object has only a couple of methods:
.set(name, value)
to set environment data.option(name, value)
to set rendering options.mount(vnode)
to change the virtual element currently mounted.use(fn)
to use a plugin. The function is called with the app
object.You can render this tree anyway you like, you just need a renderer for it. Let's use the DOM renderer for the client:
import Button from './button'
import {element,tree,render} from 'deku'
var app = tree(<Button>Hello World</Button>)
render(app, document.body)
And render the same thing to a string on the server:
import koa from 'koa'
import {element,tree,renderString} from 'deku'
let app = koa()
app.use(function *() {
this.body = renderString(tree(<Button>Hello World</Button>))
})
And you can isolate functionality by using plugins. These plugins can call set
to add data to the tree that your components can then access through their props:
app.use(analytics)
app.use(router)
app.use(api(writeKey))
You can compose components easily by just requiring them and using them in the render function:
import Button from './button'
import Sheet from './sheet'
function render (component) {
return (
<div class="MyCoolApp">
<Sheet>
<Button style="danger">One</Button>
<Button style="happy">Two</Button>
</Sheet>
</div>
)
}
Deku doesn't use any form of synthetic events because we can just capture every event in newer browsers. There are special attributes you can add to virtual elements that act as hooks to add event listeners:
function render (component) {
let {props, state} = component
return <button onClick={clicked}>{props.children}</button>
}
function clicked (event, component, updateState) {
alert('You clicked it')
}
You can view all event handlers in code.
You can access the event, the component and update the state in event handlers:
function clicked (event, component, updateState) {
let {props,state} = component
}
To access the element you'll usually want to event.target
. This is the element the event was triggered on. We also set event.delegateTarget
that will always be set to the element that owns the handler if it was a deeper element that triggered the event.
Just like the render
function, component lifecycle hooks are just plain functions:
function afterUpdate (component, prevProps, prevState, updateState) {
let {props, state} = component
if (!state.clicked) {
updateState({ clicked: true })
}
}
We have hooks for beforeMount
, afterMount
, beforeUpdate
, afterUpdate
, beforeUnmount
and two new hooks - beforeRender
and afterRender
that are called on every pass, unlike the update hooks. We've found that these extra hooks have allowed us to write cleaner code and worry less about the state of the component.
Learn more about the lifecycle hooks
Prop validation isn't handle by Deku, but because we're dealing with pure functions it's easy enough to just compose it yourself using some other validation library:
import schema from 'validate'
let {assert} = schema({
name: {
type: 'string',
required: true,
message: 'name is required'
}
})
let render = function({ props, state }) {
assert(props)
return <div></div>
}
It's often useful for components to have access to data from the outside world without needing to pass it down through components. You can set data on your tree
and components can ask for it using propTypes
.
First we set some data on the app:
app.set('currentUser', {
id: 12435,
username: 'anthonyshort',
name: 'Anthony Short'
})
Then in our components we define the prop using the source
option:
let propTypes = {
user: {
source: 'currentUser'
}
}
Whenever we change that value in our app all components that depend on it will be re-rendered with the latest value. We use this pattern to pass functions down to interact with the API:
app.set('updateProject', function (project, updates) {
api.projects.update(project, updates)
})
Which the component can access using props.updateProject
. Although it may not be as complex or optimized as Relay and GraphQL it's extremely simple and covers most use cases we've run into so far. We even use this pattern to treat the router as a data source:
router.on('/projects/:id', function (params) {
let project = api.projects.get(params.id)
app.set('currentRoute', {
name: 'view project',
project: project
})
})
This means we don't need to use some complex routing library. We just treat it like all other types of external data and components will render as needed.
Sometimes when you're rendering a list of items you want them to be moved instead of trashed during the diff. Deku supports this using the key
attribute on components:
function render (component) {
let {items} = component.props
let projects = items.map(function (project) {
return <ProjectItem key={project.id} project={project} />
})
return <div class="ProjectsList">{projects}</div>
}
At the moment we only support the key
attribute on components for simplicity. Things become slightly more hairy when moving elements around within components. So far we haven't ran into a case where this has been a major problem.
The purpose of most lifecycle hooks is usually to update the state, either by inspecting the DOM or fetching some external resources. We can simplify the concept of the lifecycle hooks by making the pure using ES7 async functions.
async function afterMount ({ props }, el) {
var items = await request(props.url)
var projects = await Projects.getAll()
// Return an object to update state
return {
items: items,
projects: projects,
loaded: true
}
}
Instead of using the updateState
function we can just return an object that will be merged in with the current state. We can do this because the lifecycle hooks are able to return a promise that resolves into a state change. All you need to do is return a promise and resolve it with an object.
We could do this with standard promises too:
function afterMount ({ props }, el) {
return request(props.url)
.then(Projects.getAll)
.then(function(items, projects){
return {
items: items,
projects: projects,
loaded: true
}
})
}
You can set a string of html to be set as innerHTML
using the innerHTML
attribute on your virtual elements:
<div innerHTML="<span>hi</span>" />
Deku doesn't do any sanitizing of the HTML string so you'll want to do that yourself to prevent XSS attacks.
Deku is built with Browserify. You can run the tests in a browser by running make test
.
MIT. See LICENSE.md
FAQs
Render interfaces using pure functions and virtual DOM
The npm package deku receives a total of 508 weekly downloads. As such, deku popularity was classified as not popular.
We found that deku demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.